
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/mman.h>
#include <asm/set_memory.h>
#include "common.h"

#define MAJOR_NUM 123
#define MINOR_NUM 0

MODULE_AUTHOR("mdy");
MODULE_DESCRIPTION("mmap demo");

static struct class * hello_class;
static struct cdev hello_cdev;
static dev_t devnum = 0;
static char * modname = "hello_mod";
static char * devicename = "hello";
static char * classname = "hello_class";

static int hello_mod_open(struct inode* inode, struct file* filp);
static int hello_mod_release(struct inode* inode, struct file* filp);
static int hello_mod_mmap(struct file *, struct vm_area_struct*);
static long hello_mod_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg);

struct file_operations hello_mod_fops =
{
    .owner = THIS_MODULE,
    .open = hello_mod_open,
    .release = hello_mod_release,
    .mmap = hello_mod_mmap,
    .unlocked_ioctl = hello_mod_ioctl,
};

static int i = 0;

static struct AddrInfo g_addrInfo = {0};
static int g_addrLen = 0;

static int hello_mod_open(struct inode* inode, struct file* filp)
{
    printk("hello_mod_open %d\n", i++);

    {
        int area_len = 1<<20;
        void* kbuff = kmalloc(area_len, GFP_KERNEL);
        unsigned long phy_addr = virt_to_phys(kbuff);
        unsigned long vir_addr = 0;

        vir_addr = vm_mmap(filp, 0, area_len, PROT_READ | PROT_WRITE, MAP_SHARED, phy_addr);
        printk("phy_addr = %lx, vir_kernel_addr = %px, vir_user_addr = %lx\n",
            phy_addr, kbuff, vir_addr);

        g_addrLen = area_len;
        g_addrInfo.phyAddr = phy_addr;
        g_addrInfo.virKernelAddr = (unsigned long)kbuff;
        g_addrInfo.virUserAddr = vir_addr;
    }

    return 0;
}

static int hello_mod_release(struct inode* inode, struct file* filp)
{
    printk("hello_mod_release %d\n", i++);

    return 0;
}


static int hello_mod_mmap(struct file *fp, struct vm_area_struct* vma)
{
    printk("hello mod mmap %d\n", i++);
    {
        //vm_pgoff是vm_mmap的最后一个参数经过右移PAGE_SHIFT后的结果
        unsigned long pfn_start = vma->vm_pgoff;
        size_t size = vma->vm_end - vma->vm_start;

        /* Remap-pfn-range will mark the range VM_IO */
        if (remap_pfn_range(vma, vma->vm_start, pfn_start, size, vma->vm_page_prot))
        {
            printk("remap_pfn_range fail!\n");
            return -EAGAIN;
        }
        return 0;
    }
}

static long hello_mod_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
    int err = 0;
    switch(cmd)
    {
        case 0xC0506D14:
        {
            copy_to_user((void*)arg, &g_addrInfo, sizeof(struct AddrInfo));
            break;
        }
        case 0xC0506D15:
        {
            printk("vm_munmap %lx\n", g_addrInfo.virUserAddr);
            vm_munmap(g_addrInfo.virUserAddr, g_addrLen);
            kfree((void*)g_addrInfo.virKernelAddr);
            break;
        }

        default:
            printk("        >in case: default\n");
            err = ENOTSUPP;
            break;
    }
    return err;
}


static int __init hello_mod_init(void)
{
    int result;
    printk("+hello_mod_init()!\n");
    devnum = MKDEV(MAJOR_NUM, MINOR_NUM);
    result = register_chrdev_region(devnum, 1, modname);

    if(result < 0)
    {
        printk("hello_mod : can't get major number!\n");
        return result;
    }

    cdev_init(&hello_cdev, &hello_mod_fops);
    hello_cdev.owner = THIS_MODULE;
    hello_cdev.ops = &hello_mod_fops;
    result = cdev_add(&hello_cdev, devnum, 1);
    if(result)
        printk("Failed at cdev_add()");
    hello_class = class_create(THIS_MODULE, classname);
    if(IS_ERR(hello_class))
    {
        printk("Failed at class_create()\n");
    }
    else
    {
        device_create(hello_class, NULL, devnum,NULL, devicename);
    }

    printk("-hello_mod_init()!\n");
    return 0;
}

static void __exit hello_mod_exit(void)
{
    printk("+hello_mod_exit!\n");
    cdev_del(&hello_cdev);
    device_destroy(hello_class, devnum);
    class_destroy(hello_class);
    unregister_chrdev_region(devnum, 1);
    printk("-hello_mod_exit!\n");
    return ;
}

module_init(hello_mod_init);
module_exit(hello_mod_exit);
MODULE_LICENSE("GPL");

